#pragma once
namespace graph
{
namespace details
{   
   inline graph_mutable_t::graph_mutable_t(graph_inf_t inf)
      :  inf_(inf)
   {
      adj_list_.reserve(inf_.vertex_count_);
   }

   inline void graph_mutable_t::make_sequential(graph_sequential_t** result, size_t& size)
   { 
      std::vector<vertex_t> vertices;
      std::vector<details::edge_real_t> edges;
      std::vector<details::edge_real_t> in_edges;
      
      size_t adj_list_offset = 0;
      size_t adj_in_list_offset = 0;
      for(size_t i = 0; i < adj_list_.size(); ++i)
      {
         vertex_t v(
            vertex_id_t(adj_list_[i].get_id()), 
            adj_list_[i].get_point(),
            adj_list_[i].get_edge_class(),
            adj_list_offset,
            adj_in_list_offset);
         vertices.push_back(v);         
         
         for(size_t j = 0; j < adj_list_[i].get_adj_edges().size(); ++j)
         {
            edges.push_back(adj_list_[i].get_adj_edges()[j]);
         }
         
         for(size_t j = 0; j < adj_list_[i].get_in_adj_edges().size(); ++j)
         {
            in_edges.push_back(adj_list_[i].get_in_adj_edges()[j]);
         }
         
         adj_list_offset += adj_list_[i].get_adj_edges().size();
         adj_in_list_offset += adj_list_[i].get_in_adj_edges().size();
      }
      
      size = sizeof(details::graph_sequential_t) +
             sizeof(vertex_t) * vertices.size() +
             sizeof(details::edge_real_t) * edges.size() +
             sizeof(details::edge_real_t) * in_edges.size();
             
      graph_sequential_t* graph = static_cast<graph_sequential_t*>(malloc(size));
      graph->inf_.id_ = inf_.id_;
      graph->inf_.vertex_count_ = vertices.size();
      graph->inf_.edge_count_ = edges.size();
      graph->inf_.max_lat_ = inf_.max_lat_;
      graph->inf_.min_lat_ = inf_.min_lat_;
      graph->inf_.max_lon_ = inf_.max_lon_;
      graph->inf_.min_lon_ = inf_.min_lon_;
      
      vertex_t* begin_of_vertices = reinterpret_cast<vertex_t*>(graph->data_);                  
      for(size_t i = 0; i < vertices.size(); ++i)
      {
         begin_of_vertices[i] = vertices[i];
      } 
      
      details::edge_real_t* begin_of_edges = reinterpret_cast<details::edge_real_t*>(begin_of_vertices + vertices.size());    
      for(size_t i = 0; i < edges.size(); ++i)
      {
         begin_of_edges[i] = edges[i];
      }
      
      details::edge_real_t* begin_of_in_edges = begin_of_edges + edges.size();
      for(size_t i = 0; i < in_edges.size(); ++i)
      {
         begin_of_in_edges[i] = in_edges[i];
      }
      
      *result = graph;
   }
   inline void graph_mutable_t::make_not_sequential(graph_not_sequential_t** result, 
                                                    vertex_t** vertices, edge_real_t** edges)
   { 
      //to use not_sequential storage type, firstly implement 
      //saving of adj_in_list for vertices of not_sequential graph
      throw graph_exception_t("Not implemented");
      std::vector<vertex_t> vertices_temp;
      std::vector<details::edge_real_t> edges_temp;
      
      size_t adj_list_offset = 0;
      for(size_t i = 0; i < adj_list_.size(); ++i)
      {
         vertex_t v(
            vertex_id_t(adj_list_[i].get_id()),
            adj_list_[i].get_point(),
            adj_list_[i].get_edge_class(),
            adj_list_offset,
            0 //IF YOU IMPLEMENTING not_sequential STORAGE TYPE, REPLACE THIS WITH APPROPRIATE in_adj_list offset
            );
         vertices_temp.push_back(v);         
         
         for(size_t j = 0; j < adj_list_[i].get_adj_edges().size(); ++j)
         {
            edges_temp.push_back(adj_list_[i].get_adj_edges()[j]);
         }
         adj_list_offset += adj_list_[i].get_adj_edges().size();
      }
      
      *vertices = static_cast<vertex_t*>(malloc(sizeof(vertex_t) * vertices_temp.size()));
      *edges = static_cast<details::edge_real_t*>(malloc(sizeof(details::edge_real_t) * edges_temp.size()));
                      
      for(size_t i = 0; i < vertices_temp.size(); ++i)
      {
         (*vertices)[i] = vertices_temp[i];
      } 
      
      for(size_t i = 0; i < edges_temp.size(); ++i)
      {
         (*edges)[i] = edges_temp[i];
      }
      inf_.vertex_count_ = vertices_temp.size();
      inf_.edge_count_ = edges_temp.size();
      //inf_.max_lat_ = inf_.max_lat_;
      //inf_.min_lat_ = inf_.min_lat_;
      //inf_.max_lon_ = inf_.max_lon_;
      //inf_.min_lon_ = inf_.min_lon_;
      *result = new graph_not_sequential_t(inf_, *vertices, *edges);
   }


   inline std::vector<vertex_mutable_t>& graph_mutable_t::get_adj_list()
   {
      return adj_list_;
   }

   inline vertex_mutable_t& graph_mutable_t::add_vertex(point_t p)
   {
      details::vertex_mutable_t new_v(vertex_id_t(adj_list_.size()), p);
      adj_list_.push_back(new_v);
      return adj_list_.back();
   }
   
   inline void graph_mutable_t::save_sequential_to_file(std::string& file_name)
   {
      graph_sequential_t* seq_graph;
      size_t seq_graph_size;
      make_sequential(&seq_graph, seq_graph_size);
     
      //create file
      std::filebuf fbuf;
      fbuf.open(file_name.c_str(), std::ios_base::in | std::ios_base::out |
                                   std::ios_base::trunc | std::ios_base::binary); 
      //Set the size
      fbuf.pubseekoff(seq_graph_size-1, std::ios_base::beg);
      fbuf.sputc(0);
      fbuf.close();
      
      //open as mapped file
      boost::interprocess::file_mapping m_file(file_name.c_str(), boost::interprocess::read_write);
      boost::interprocess::mapped_region region(m_file, boost::interprocess::read_write, 0, 0);

      void* begin = region.get_address();
               
      memcpy(begin, seq_graph, seq_graph_size);
      region.flush();
   }
   inline void graph_mutable_t::save_not_sequential_to_file(std::string& file_name)
   {
      throw graph_exception_t("Not implemented");
   }
}
//graph_t
inline graph_t::graph_t(std::string file_name, std::ostream& logger)
{
   details::graph_sequential_t::load_from_file(file_name, &graph_, graph_finalizer_);
   meta_data_ = details::graph_metadata_ptr_t(new details::graph_metadata_t(this, true) );
   //meta_data_vect_ = details::graph_metadata_ptr_t(new details::graph_metadata_t(this, false) );
   logger << "vertex count: " << graph_->get_vertex_count() << std::endl;
   logger << "edge count: " << graph_->get_edge_count() << std::endl;
}
inline graph_id_t graph_t::get_graph_id() const
{
   return graph_->get_graph_id();
}
inline size_t graph_t::get_edge_count() const
{
   return graph_->get_edge_count();
}
inline size_t graph_t::get_vertex_count() const
{
   return graph_->get_vertex_count();
}
inline vertex_t const & graph_t::operator [] (size_t idx) const
{
   return (*graph_)[idx];
}
inline vertex_t & graph_t::operator [] (size_t idx)
{
   return (*graph_)[idx];
}
inline vertex_t const & graph_t::get_vertex_by_id(size_t idx) const
{
   return (*graph_)[idx];
}
inline vertex_t & graph_t::get_vertex_by_id(size_t idx)
{
   return (*graph_)[idx];
}
inline std::pair<details::edge_real_t*, details::edge_real_t*> graph_t::get_vertex_edges(vertex_in_graph_id_t idx) const
{
   return (*graph_).get_vertex_edges(idx);
}
inline std::pair<details::edge_real_t*, details::edge_real_t*> graph_t::get_vertex_in_edges(vertex_in_graph_id_t idx) const
{
   return (*graph_).get_vertex_in_edges(idx);
}
inline details::graph_metadata_t const & graph_t::get_metadata() const
{
   return *meta_data_;
}
//O(V)
inline bool graph_t::get_nearest_vertex(point_t p, vertex_id_t& result) const
{
   vertex_id_t nearest_id = 0;
   lat_lon_t min_distance = MAX_FLOAT;
   lat_lon_t temp_distance;
   point_t temp_point;
   bool entered = false;
   vertex_iterator_pair_t v_it = vertices(const_cast<graph_t&>(*this));
   for(; v_it.first != v_it.second; ++v_it.first)
   {
      entered = true;
      temp_point = (*v_it.first).get_point();
      temp_distance = math::distance(temp_point.lat_, temp_point.lon_, p.lat_, p.lon_);
      if (temp_distance < min_distance)
      {
         min_distance = temp_distance;
         nearest_id = (*v_it.first).get_id();
      }
   }  
   result = nearest_id;
   return entered;
}
//O(1)
//not continious
inline bool graph_t::get_nearest_edges(point_t p, std::vector<edge_t> & result) const
{
   details::cell_id_t cell_id = details::graph_metadata_t::get_cell_id<details::d1>(p);
   details::cell_edge_iterator_pair_t it = meta_data_->cell_edges<details::d1>(cell_id);
   if (it.first != it.second)
   {
      for(; it.first != it.second; ++it.first)
      {
         result.push_back( (*it.first).second );
      } 
      return true;
   }
   
   cell_id = details::graph_metadata_t::get_cell_id<details::d2>(p);
   it = meta_data_->cell_edges<details::d2>(cell_id);
   if (it.first != it.second)
   {
      for(; it.first != it.second; ++it.first)
      {
         result.push_back( (*it.first).second );
      }
      return true;
   }
   
   cell_id = details::graph_metadata_t::get_cell_id<details::d4>(p);
   it = meta_data_->cell_edges<details::d4>(cell_id);
   if (it.first != it.second)
   {
      for(; it.first != it.second; ++it.first)
      {
         result.push_back( (*it.first).second );
      }
      return true;
   }      
   return false;
}

inline vertex_t& source(edge_t e, graph_t& g)
{
    return g[e.get_source_vertex_id()];
}

inline vertex_t const & source(edge_t const e, graph_t const & g)
{
   return g[e.get_source_vertex_id()];
}

inline vertex_t& target(edge_t e, graph_t& g)
{
   return g[e.get_target_vertex_id()];
}
inline vertex_t const & target(edge_t const e, graph_t const & g)
{
   return g[e.get_target_vertex_id()];
}
}
